﻿////////////////////////////////////////////////////////////////////////////////////////////////
//
// Copyright © Yaroslavov Alexander 2010
//
// Contacts:
// Phone: +7(906)827-27-51
// Email: x-ronos@yandex.ru
//
/////////////////////////////////////////////////////////////////////////////////////////////////


using System.Collections;
using System.Collections.Generic;
using System;

namespace Moppet.Lapa
{
    /*
     * Обратите внимание, что в структуре не используются свойства. Они реально не нужны, т.к. особо ни от чего не спасают,
     * а тормозов добавляют массу.
     */

    /// <summary>
	/// Структура, описывает блок текста.
	/// Внимание: операторы приведения к строке не реализовывать, операторы сравнения не реализовывать - это приводит к ошибкам понимания
	/// как работает структура и неоптимальному использованию!
	/// </summary>
	public struct LpText : IEnumerable<char>
	{
		/// <summary>
		/// Источник
		/// </summary>
		public string Source;

		/// <summary>
		/// Индекс на первый символ блока
		/// </summary>
		public int Index;

		/// <summary>
		/// Размер блока
		/// </summary>
		public int Length;


        /// <summary>
        /// Конструирует новый блок текста из другого, задавая смещение начала.
        /// </summary>
        /// <param name="text">Текст.</param>
        /// <param name="startOffset">Смещение начала. Если назад, то длина текста увеличится, если вперёд, то уменьшится.</param>
        public LpText(LpText text, int startOffset)
        {
            Source = text.Source; Index = text.Index + startOffset; Length = text.Length - startOffset;
        }

		/// <summary>
		/// Основной конструктор.
		/// </summary>
		/// <param name="source">Источник текста.</param>
		/// <param name="index">Индекс на первый символ блока.</param>
		/// <param name="length">Длинна блока.</param>
		public LpText(string source, int index, int length)
		{
			Source = source; Index = index; Length = length;
		}
		
		/// <summary>
		/// Вспомогательный конструктор.
		/// </summary>
		/// <param name="source">Источник текста.</param>
		public LpText(string source)
		{
			Source = source; Index = 0; Length = source.Length;
		}
		
		/// <summary>
		/// Возвращает символ по относительному индексу [0; Length).
		/// </summary>
		/// <param name="relativeIndex">Индекс символа [0; Length).</param>
		/// <returns>Символ.</returns>
		public char this[int relativeIndex]
		{
			get { return Source[Index + relativeIndex]; }
		}
		
		/// <summary>
		/// Оператор приведения строки в объект.
		/// </summary>
		/// <param name="text">Строка.</param>
		/// <returns>Объект LpText.</returns>
		public static implicit operator LpText(string text)
		{
			return new LpText(text);
		}

		/// <summary>
		/// Возвращает урезаный от начала блок текста.
		/// </summary>
		/// <param name="n">Количество символов, которое нужно пропустить.</param>
		/// <returns>Уменьшенный на n символов блок текста.</returns>
		public LpText Skip(int n)
		{
			return new LpText(Source, Index + n, Length - n);
		}

		/// <summary>
		/// Возвращает истину, если данный блок текста начинается с подстроки subStr.
		/// </summary>
		/// <param name="subStr">Подстрока.</param>
		/// <returns>Булево значение.</returns>
		public bool StartsWith(string subStr)
		{
			return string.CompareOrdinal(Source, Index, subStr, 0, subStr.Length) == 0;
		}

        /// <summary>
        /// Возвращает истину, если данный блок текста начинается с подстроки subStr.
        /// </summary>
        /// <param name="subStr">Подстрока.</param>
        /// <param name="ignoreCase">Истина, если нужно включить нечувствительность к регистру.</param>
        /// <returns>Булево значение.</returns>
        [Obsolete("Непроверена и пока нигде не используется.")]
        public bool StartsWith(string subStr, bool ignoreCase)
        {
            return string.Compare(Source, Index, subStr, 0, subStr.Length, ignoreCase: ignoreCase) == 0;
        }

		/// <summary>
		/// Строка, всегда не null.
		/// </summary>
		/// <returns>Строка, всегда не null.</returns>
		public override string ToString()
		{
			return Length > 0 ? Source.Substring(Index, Length) : "";
		}

		/// <summary>
		/// Пустая структура.
		/// </summary>
		public readonly static LpText Empty = new LpText(null, 0, 0);


		#region Операции сравнения подстроки с подстрокой
		
		/// <summary>
		/// Операция сравнения подстроки с подстрокой.
		/// </summary>
		/// <param name="t">Блок.</param>
		/// <param name="s">Строка.</param>
		/// <returns>Истина, если строки равны.</returns>
		public static bool operator == (LpText t, string s)
		{
			return t.ToString() == s;
		}

		/// <summary>
		/// Операция сравнения подстроки с подстрокой.
		/// </summary>
		/// <param name="s">Строка.</param>
		/// <param name="t">Блок.</param>
		/// <returns>Истина, если строки равны.</returns>
		public static bool operator ==(string s, LpText t)
		{
			return t.ToString() == s;
		}

		/// <summary>
		/// Операция сравнения подстроки с подстрокой.
		/// </summary>
		/// <param name="t">Блок.</param>
		/// <param name="s">Строка.</param>
		/// <returns>Ложь, если строки равны.</returns>
		public static bool operator != (LpText t, string s)
		{
			return t.ToString() != s;
		}

		/// <summary>
		/// Операция сравнения подстроки с подстрокой.
		/// </summary>
		/// <param name="s">Строка.</param>
		/// <param name="t">Блок.</param>
		/// <returns>Ложь, если строки равны.</returns>
		public static bool operator !=(string s, LpText t)
		{
			return t.ToString() != s;
		}

		#endregion Операции сравнения подстроки с подстрокой

		/// <summary>
		/// Поэлементное сравнение структур.
		/// Содержимое Source не участвует в сравнении, т.е. сравниваются адреса (ссылки).
		/// </summary>
		/// <param name="obj">Объект, указывающий на структуру LpText.</param>
		/// <returns>Истина, если обе структуры абсолютно идентичны и Source ссылается на одну и ту же память.</returns>
		public override bool Equals(object obj)
		{
			LpText t = (LpText)obj;
			return this.Index == t.Index && this.Length == t.Length && object.ReferenceEquals(this.Source, t.Source);
		}

		/// <summary>
		/// Хеш. Source в хешировании не участвует из-за тормозов, т.к. данная структура предназначена,
		/// чтобы отличать блоки текста относительно одного источника.
		/// </summary>
		/// <returns>Хеш.</returns>
		public override int GetHashCode()
		{
			return Index ^ Length;
		}

        /// <summary>
        /// Перечислитель символов.
        /// </summary>
        /// <returns>Перечислитель символов.</returns>
        public IEnumerator<char> GetEnumerator()
        {
            for (int i = 0; i < Length; ++i)
                yield return Source[Index + i];
        }

        /// <summary>
        /// Перечислитель символов.
        /// </summary>
        /// <returns>Перечислитель символов.</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
